package com.shengapp.util;

import com.shengapp.models.ApiRet;
import org.json.JSONArray;
import org.json.JSONObject;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

/**
 * @Author: sheng-app
 * @Date: 2021/7/26 14:02
 */
public class HttpUtil {
    private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray();

    private final String code;
    private final String key;
    private final String url;

    private final Logger logger = Logger.getLogger(HttpUtil.class.getName());

    public HttpUtil(String url, String code, String key) {
        this.url = url;
        this.code = code;
        this.key = key;
    }

    public ApiRet send(String path, Map<String, String> params, boolean post) {
        params.put("partnerCode", code);
        params.put("sign", signParamsMd5(params, key, true));
        ApiRet apiRet;
        if (post) {
            apiRet = postForm(url + path, params, null);
        } else {
            apiRet = get(url + path, params, null);
        }
        params.clear();
        return apiRet;
    }

    public String signParamsMd5(Map<String, ?> params, String key, boolean encodeValue) {
        String paramStr = this.formatParamsToString(params, encodeValue);
        paramStr = paramStr + "&key=" + key;

        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            return printHexBinary(md5.digest(paramStr.getBytes())).toUpperCase();
        } catch (Exception e) {
            logger.warning(e.getMessage());
            return null;
        }
    }

    private String formatParamsToString(Map<String, ?> params, boolean encodeValue) {
        StringBuilder url = new StringBuilder();
        TreeMap<String, ?> paramsSorted = new TreeMap<>(params);
        paramsSorted.forEach((key, value) -> {
            url.append(url.length() > 0 ? "&" : "");
            url.append(key);
            url.append("=");
            url.append(value == null ? "" : (encodeValue ? this.encodeUrl(value.toString()) : value));
        });
        return url.toString();
    }

    public String encodeUrl(String url) {
        String result = "";

        try {
            result = URLEncoder.encode(url, StandardCharsets.UTF_8);
            result = result.replaceAll("\\*", "%2A");
            result = result.replaceAll("\\+", "%2B");
        } catch (Exception e) {
            logger.warning(e.getMessage());
        }

        return result;
    }

    public String printHexBinary(byte[] data) {
        StringBuilder r = new StringBuilder(data.length * 2);
        for (byte b : data) {
            r.append(HEX_CODE[b >> 4 & 15]);
            r.append(HEX_CODE[b & 15]);
        }
        return r.toString();
    }

    private static String formatParamsToString(Map<String, ?> params) {
        if (params == null) {
            return "";
        }
        StringBuilder url = new StringBuilder();
        TreeMap<String, ?> paramsSorted = new TreeMap<>(params);
        paramsSorted.forEach((key, value) -> {
            url.append(url.length() > 0 ? "&" : "");
            url.append(key);
            url.append("=");
            url.append(value == null ? "" : URLEncoder.encode(value.toString(), StandardCharsets.UTF_8));
        });
        return url.toString();
    }

    private static class MyX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }

    private ApiRet connect(String url, Map<String, String> params, String body, Map<String, String> headers, boolean post) {
        ApiRet apiRet;
        HttpURLConnection connection = null;
        try {
            //参数
            final String paramStr = formatParamsToString(params);
            logger.info("params: " + paramStr);
            logger.info((post ? "POST" : "GET") + " url: " + url);
            if (!post && !paramStr.isEmpty()) {
                url += "?" + paramStr;
            }

            URL uri = new URL(url);
            connection = (HttpURLConnection) uri.openConnection();
            // headers
            if (headers != null) {
                headers.forEach(connection::setRequestProperty);
            }
            // body
            connection.setRequestMethod(post ? "POST" : "GET");
            connection.setDoOutput(post);
            if (post) {
                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
                dataOutputStream.write(body == null ? paramStr.getBytes() : body.getBytes());
                dataOutputStream.flush();
                dataOutputStream.close();
            }
            // https
            if (url.startsWith("https")) {
                SSLContext sslcontext = SSLContext.getInstance("SSL", "SunJSSE");
                TrustManager[] tm = {new MyX509TrustManager()};
                sslcontext.init(null, tm, new SecureRandom());
                HostnameVerifier ignoreHostnameVerifier = (s, session) -> true;
                final HttpsURLConnection https = (HttpsURLConnection) connection;
                https.setSSLSocketFactory(sslcontext.getSocketFactory());
                https.setHostnameVerifier(ignoreHostnameVerifier);
                https.connect();
            } else {
                connection.connect();
            }
            apiRet = getResult(connection);
        } catch (Exception e) {
            logger.warning(e.getMessage());
            apiRet = ApiRet.fail().setCode(ApiRet.CODE_SYSTEM).setMessage("获取链接失败：" + e.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return apiRet;
    }


    public ApiRet get(String url, Map<String, String> params, Map<String, String> headers) {
        StringBuilder paramStr = new StringBuilder();
        params.forEach((k, v) -> {
            paramStr.append(paramStr.length() == 0 ? "?" : "&");
            paramStr.append(k).append("=").append(v);
        });
        return this.get(url + paramStr, headers);
    }

    public ApiRet get(String url, Map<String, String> headers) {
        headers = addHeaders(headers);
        return connect(url, null, null, headers, false);
    }

    public ApiRet postForm(String url, Map<String, String> params, Map<String, String> headers) {
        headers = addHeaders(headers);
        return connect(url, params, null, headers, true);
    }

    public static boolean isBlank(String str) {
        if (str == null) {
            return true;
        } else {
            int i = 0;

            for (int len = str.length(); i < len; ++i) {
                if (str.charAt(i) > ' ') {
                    return false;
                }
            }

            return true;
        }
    }

    private String readContent(InputStream inputStream) throws IOException {
        if (inputStream == null) {
            return null;
        } else {
            StringBuilder content = new StringBuilder();
            String lineContent;
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            while ((lineContent = reader.readLine()) != null) {
                content.append(lineContent);
            }
            return content.toString();
        }
    }

    private ApiRet getResult(HttpURLConnection connection) throws IOException {
        int successCode = 200;
        int code = connection.getResponseCode();
        InputStream inputStream = connection.getInputStream();
        // 判断是否gzip
        String contentEncoding = connection.getHeaderField("Content-Encoding");
        if (contentEncoding != null && contentEncoding.contains("gzip")) {
            inputStream = new GZIPInputStream(inputStream);
        }
        String content = readContent(inputStream);
        ApiRet apiRet;
        if (code == successCode) {
            apiRet = ApiRet.ok();
            if (content != null && !content.isEmpty()) {
                //json
                String jsonFlag = "{";
                String arrayFlag = "[";
                if (content.startsWith(jsonFlag)) {
                    JSONObject jsonObject = new JSONObject(content);
                    // 标准API RET
                    String successFlag = "success";
                    String dataFlag = "data";
                    String codeFlag = "code";
                    final boolean hasContent = jsonObject.has(dataFlag) || jsonObject.has(codeFlag);
                    if (jsonObject.has(successFlag) && hasContent) {
                        apiRet = new ApiRet(content);
                    } else {
                        apiRet.setData(jsonObject);
                    }
                } else if (content.startsWith(arrayFlag)) {
                    apiRet.setData(new JSONArray(content));
                } else {
                    apiRet.putData("html", content);
                }
            }
        } else {
            apiRet = ApiRet.fail().setCode(ApiRet.CODE_SYSTEM).setMessage("Code: " + code);
            apiRet.put("detail", content);
        }
        String apiRetStr = apiRet.toString();
        logger.info("http response:" + (apiRetStr.length() > 500 ? apiRetStr.substring(0, 500) : apiRetStr));
        return apiRet;
    }

    private Map<String, String> addHeaders(Map<String, String> headers) {
        if (headers == null) {
            headers = new HashMap<>(10);
        }
        headers.putIfAbsent("Accept", "*/*");
        headers.putIfAbsent("Pragma", "no-cache");
        headers.putIfAbsent("Connection", "close");
        headers.putIfAbsent("Cache-Control", "no-cache");
        headers.putIfAbsent("accept-encoding", "gzip");
        headers.putIfAbsent("User-Agent", "SDK 1.0.5");
        return headers;
    }
}
